// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -n -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "main.h"
#include "addrspace.h"
#include "machine.h"
#include "noff.h"

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void 
SwapHeader (NoffHeader *noffH)
{
    noffH->noffMagic = WordToHost(noffH->noffMagic);
    noffH->code.size = WordToHost(noffH->code.size);
    noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
    noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
    noffH->initData.size = WordToHost(noffH->initData.size);
    noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
    noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
    noffH->uninitData.size = WordToHost(noffH->uninitData.size);
    noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
    noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Set up the translation from program memory to physical 
//	memory.  For now, this is really simple (1:1), since we are
//	only uniprogramming, and we have a single unsegmented page table
//----------------------------------------------------------------------
/* static */ int AddrSpace::spaces = 0;

AddrSpace::AddrSpace()
{
    pageTable = new TranslationEntry[NumPhysPages];
    id = ++AddrSpace::spaces;
    for (int i = 0; i < NumPhysPages; i++) {
		pageTable[i].virtualPage = i;	// for now, virt page # = phys page #
		pageTable[i].physicalPage = i;
		//pageTable[i].valid = TRUE;
		pageTable[i].valid = FALSE;
		pageTable[i].use = FALSE;
		pageTable[i].dirty = FALSE;
		pageTable[i].readOnly = FALSE; 
    }
    
}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
// 	Dealloate an address space.
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
	AddrSpace::spaces--;
	for (unsigned int i = 0; i < numPages; i++) {
		kernel->pageStack->Append(pageTable[i].physicalPage);
	}
   delete pageTable;
}


//----------------------------------------------------------------------
// AddrSpace::Load
// 	Load a user program into memory from a file.
//
//	Assumes that the page table has been initialized, and that
//	the object code file is in NOFF format.
//
//	"fileName" is the file containing the object code to load into memory
//----------------------------------------------------------------------

bool 
AddrSpace::Load(char *fileName) 
{
    OpenFile *executable = kernel->fileSystem->Open(fileName);
    NoffHeader noffH;
    unsigned int size;

    if (executable == NULL) {
    	cerr << "Unable to open file " << fileName << "\n";
    	return FALSE;
    }

    executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
    	SwapHeader(&noffH);
    	
    ASSERT(noffH.noffMagic == NOFFMAGIC);

	// how big is address space?
    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize;	// we need to increase the size
																							// to leave room for the stack
    numPages = divRoundUp(noffH.code.size, PageSize) 
    	+ divRoundUp(noffH.initData.size, PageSize) 
    	+ divRoundUp(noffH.uninitData.size, PageSize) 
    	+ divRoundUp(UserStackSize, PageSize);
    size = numPages * PageSize;

    ASSERT(numPages <= NumPhysPages); // check we're not trying
						// to run anything too big --
						// at least until we have
						// virtual memory

    DEBUG(dbgAddr, "Initializing address space: " << numPages << ", " << size);

    if (noffH.uninitData.size > 0)
    {
    	DEBUG(dbgAddr, "Initializing uninit");
	    DEBUG(dbgAddr, noffH.uninitData.virtualAddr << ", " << noffH.uninitData.size);
	    
	    int pages = divRoundUp(noffH.uninitData.size, PageSize);
	    for (int i = 0; i < pages; i++)
	   	{
	   		int addr = kernel->pageStack->RemoveFront();
	   		int vaddr = noffH.uninitData.virtualAddr / PageSize + i;
			DEBUG(dbgAddr, "Address, vaddress: " << addr  << ", " << vaddr <<  "\n");
			
			bzero(&kernel->machine->mainMemory[addr * PageSize], PageSize);
			pageTable[vaddr].virtualPage =  vaddr;
			pageTable[vaddr].physicalPage = addr;
			pageTable[vaddr].valid = TRUE;
		}
    }

	// then, copy in the code and data segments into memory
    if (noffH.code.size > 0)
    {
    	DEBUG(dbgAddr, "Initializing code segment.");
    	DEBUG(dbgAddr, noffH.code.virtualAddr << ", " << noffH.code.size);
        /*executable->ReadAt(
		&(kernel->machine->mainMemory[noffH.code.virtualAddr]), 
			noffH.code.size, noffH.code.inFileAddr);*/
			
		int pages = divRoundUp(noffH.code.size, PageSize);
		DEBUG(dbgAddr, "Pages: " << pages << "\n");
		
		for (int i = 0; i < pages; i++)
		{
			int addr = kernel->pageStack->RemoveFront();
			int vaddr = noffH.code.virtualAddr / PageSize + i;
			
			executable->ReadAt(&kernel->machine->mainMemory[addr * PageSize], PageSize, noffH.code.inFileAddr + i*PageSize);
			DEBUG(dbgAddr, "Address, vaddress: " << addr  << ", " << vaddr <<  "\n");
			pageTable[vaddr].virtualPage = vaddr;
			pageTable[vaddr].physicalPage = addr;
			pageTable[vaddr].valid = TRUE;
		}
	}
	
	if (noffH.initData.size > 0)
	{
		DEBUG(dbgAddr, "Initializing data segment.");
		DEBUG(dbgAddr, noffH.initData.virtualAddr << ", " << noffH.initData.size);
        /*executable->ReadAt(
		&(kernel->machine->mainMemory[noffH.initData.virtualAddr]),
			noffH.initData.size, noffH.initData.inFileAddr);*/

		int pages = divRoundUp(noffH.initData.size, PageSize);
		for (int i = 0; i < pages; i++)
		{
			int addr = kernel->pageStack->RemoveFront();
			int vaddr = noffH.initData.virtualAddr / PageSize + i;
			
			DEBUG(dbgAddr, "Address, vaddress: " << addr  << ", " << vaddr <<  "\n");
			executable->ReadAt(&kernel->machine->mainMemory[addr * PageSize], PageSize, noffH.initData.inFileAddr + i*PageSize);
			pageTable[vaddr].virtualPage = vaddr;
			pageTable[vaddr].physicalPage = addr;
			pageTable[vaddr].valid = TRUE;
		}
	}
	
	int pages = divRoundUp(UserStackSize, PageSize);
	DEBUG(dbgAddr, "Initializing stack segment.");
	DEBUG(dbgAddr, NumPhysPages*PageSize << ", " << UserStackSize);

    for (int i = 0; i < pages; i++)
    {
    	int addr = kernel->pageStack->RemoveFront();
    	int vaddr = numPages - i - 1;
    	DEBUG(dbgAddr, "Stack: Address, vaddress: " << addr  << ", " << vaddr <<  "\n");
    	pageTable[vaddr].virtualPage = vaddr;
    	pageTable[vaddr].physicalPage = addr;
    	pageTable[vaddr].valid = TRUE;
    }
    
    stackPage  = numPages;
    
    delete executable;		// close file
    return TRUE;			// success
}

//----------------------------------------------------------------------
// AddrSpace::Execute
// 	Run a user program.  Load the executable into memory, then
//	(for now) use our own thread to run it.
//
//	"fileName" is the file containing the object code to load into memory
//----------------------------------------------------------------------
void 
AddrSpace::Execute(char *fileName)
{
	char* argv[1] = { fileName };
	Execute(fileName, 1, argv);
}


void 
AddrSpace::Execute(char *fileName, int argc, char* argv[])
{
    if (!Load(fileName)) {
		return;				// executable not found
    }

	/*cout << "Linia de comanda de excutat:";
	for(int i = 0; i < argc; i++)
	{
		cout << " " << argv[i];
	}
	cout << endl << flush;*/
	
	kernel->currentThread->space = this;

    //this->InitRegisters();		// set the initial register values
    this->RestoreState();		// load page table register

    this->InitRegisters();

    kernel->machine->Run();		// jump to the user progam

    ASSERTNOTREACHED();			// machine->Run never returns;
					// the address space exits
					// by doing the syscall "exit"
}


//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters()
{
    Machine *machine = kernel->machine;
    int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

   // Set the stack register to the end of the address space, where we
   // allocated the stack; but subtract off a bit, to make sure we don't
   // accidentally reference off the end!
    //machine->WriteRegister(StackReg, numPages * PageSize - 16);
    machine->WriteRegister(StackReg, stackPage * PageSize - 16);
    //DEBUG(dbgAddr, "Initializing stack pointer: " << numPages * PageSize - 16);
    DEBUG(dbgAddr, "Initializing stack pointer: " << stackPage * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, don't need to save anything!
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{
	for (int i = 0; i < NumTotalRegs; i++)
		registers[i] = kernel->machine->ReadRegister(i);
}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{
    kernel->machine->pageTable = pageTable;
    kernel->machine->pageTableSize = numPages;

    for (int i = 0; i < NumTotalRegs; i++)
    	kernel->machine->WriteRegister(i, registers[i]);
}

void AddrSpace::PrintInfo()
{
	cout << "Address space [" << id << "]:" << endl;
	cout << "\tNumber of pages: " << numPages << endl;
	cout << "\tStack page: " << stackPage << endl;
}


TranslationStatus AddrSpace::Translate(int vaddr,int *paddr, bool write)
{
	unsigned int pagenum;
	int offset;
	int framenum;
	
	ASSERT(vaddr >= 0);
	ASSERT(paddr != (int *)0);
	pagenum = vaddr / PageSize;
	if (pagenum >= numPages)
	{
		return AddressError;
	}
	
	if (!pageTable[pagenum].valid)
	{
		cout << "Page fault exception addr space\n";
		return PageFault;
	}
	
	if (write && pageTable[pagenum].readOnly)
	{
		return ProtectionError;
	}
	
	framenum = pageTable[pagenum].physicalPage;
	if ((framenum >= NumPhysPages) || (framenum < 0))
	{
		return BusError;
	}
	
	pageTable[pagenum].use = TRUE;
	if (write)
	{
		pageTable[pagenum].dirty = TRUE;
	}
	
	offset = vaddr % PageSize;
	*paddr = framenum * PageSize + offset;
	ASSERT(*paddr < MemorySize);
	ASSERT(*paddr >= 0);
	return OK;
}

TranslationStatus AddrSpace::ReadMem(int addr, int size, int *value)
{
	int physicalAddress;
	TranslationStatus status;
	
	ASSERT(value != (int *)0);
	ASSERT(addr >= 0);
	status = Translate(addr, &physicalAddress, FALSE);
	if (status != OK) {
		return(status);
	}
	
	switch (size)
	{
		case 1:
			*value = kernel->machine->mainMemory[physicalAddress];
			break;
			
		case 2:
			if (addr & 0x1)
			{
				// alignment error
				return(AddressError);
			}
			
			*value = ShortToHost(*(unsigned short *) &kernel->machine->mainMemory[physicalAddress]);
			break;
			
		case 4:
			if (addr & 0x3)
			{
				// alignment error
				return AddressError;
			}
			
			*value = WordToHost(*(unsigned int *) &kernel->machine->mainMemory[physicalAddress]);
			break;
			
		default: ASSERT(FALSE);
	}
	return OK;
}

TranslationStatus AddrSpace::WriteMem(int addr, int size, int value)
{
	int physicalAddress;
	TranslationStatus status;
	
	ASSERT(addr >= 0);
	status = Translate(addr, &physicalAddress,TRUE);
	if (status != OK)
	{
		return status;
	}
	
	switch (size) 
	{
		case 1:
			kernel->machine->mainMemory[physicalAddress] = (unsigned char)(value & 0xff);
			break;
			
		case 2:
			if (addr & 0x1)
			{
				// alignment error
				return AddressError;
			}
			
			*(unsigned short *) &(kernel->machine->mainMemory[physicalAddress]) = ShortToMachine((unsigned short)(value & 0xffff));
			break;
			
		case 4:
			if (addr & 0x3)
			{
				// alignment error
				return AddressError;
			}
			
			*(unsigned int *) &(kernel->machine->mainMemory[physicalAddress]) = WordToMachine((unsigned int)value);
			break;
			
		default: ASSERT(FALSE);
	}
	return OK;
}

int AddrSpace::UserStringToKernel(int vaddr, char* buf)
{
	int length = 0;
	int dat;
	TranslationStatus status;
	
	DEBUG(dbgAddr, "Reading user string to kernel, starting virtual address: " << vaddr);
	while(length < MaxStringArgLength)
	{
		status = ReadMem(vaddr, 1, &dat);
		// n.b. this will fail if user string's page(s) are not in
		// memory.
		if (status != OK)
		{
			return(-1);
		}
		
		*buf = (char)dat;
		vaddr++;
		length++;
		if (*buf == '\0') break;
		buf++;
	}
	
	if (length >= MaxStringArgLength)
	{
		return -1;
	}
	else
	{
		return length;
	}
}

int AddrSpace::UserBufToKernel(int vaddr, int length, char* buf)
{
	int knt;
	TranslationStatus status;
	int dat;
	
	DEBUG(dbgAddr, "Reading user buffer to kernel, starting virtual address: " << vaddr << " ,length " << length << " bytes.");
	for(knt = 0; knt < length; knt++)
	{
		status = ReadMem(vaddr, 1, &dat);
		// n.b. this will fail if user buf's page(s) are not in
		// memory.
		if (status != OK)
		{
			return -1;
		}
		
		*buf = (char)dat;
		vaddr++;
		buf++;
	}
	
	return length;
}

int AddrSpace::KernelToUserBuf(int vaddr, int length, char* buf)
{
	TranslationStatus status;
	int knt;
	
	DEBUG(dbgAddr, "Writing user buffer from kernel, starting virtual address: " << vaddr << " ,length " << length << " bytes.");
	for(knt = 0; knt < length; knt++)
	{
		status = WriteMem(vaddr, 1, (int)(*buf));
		// n.b. this will fail if user buf's page(s) are not in
		// memory.
		if (status != OK)
		{
			return -1;
		}
		vaddr++;
		buf++;
	}
	
	return(length);
}
